package com.wmh.baseservice.operationaid.pojo.vo.store;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.ImmutableMap;
import com.wmh.baseservice.common.ex.ServiceException;
import com.wmh.baseservice.operationaid.entity.TbStoreData;
import com.wmh.baseservice.operationaid.enums.ContrastRelation;
import com.wmh.baseservice.operationaid.enums.ContrastUnit;
import com.wmh.baseservice.operationaid.utils.CalculateUtils;
import io.swagger.annotations.ApiModel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;

/**
 * @author 谢骏
 * @date 2023/8/2 16:20
 **/
@Data
@Accessors(chain = true)
@ApiModel(value = "淘宝店铺数据周期对比响应体封装")
@Slf4j
public class TbStoreSameDateComparisonRespVO {

    private List<Column> columns;

    private List<Map<String, Element>> tableData;

    @Data
    @Accessors(chain = true)
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Element {
        private Object value;
        private BigDecimal poor;
        private BigDecimal percentage;
    }

    @Data
    @Accessors(chain = true)
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Column {
        private String label;
        private String width;
        private String fixed;
    }

    /**
     * 编辑响应对象
     *
     * @param list         数据库查出用于转化的列表
     * @param reqStartDate 请求参数中的 起始时间
     * @param reqEndDate   请求参数中的 结束时间
     * @param unit         对于单位
     * @param relation     对比关系
     * @return 响应体
     */
    public static TbStoreSameDateComparisonRespVO builder(List<TbStoreData> list, LocalDateTime reqStartDate, LocalDateTime reqEndDate, ContrastUnit unit, ContrastRelation relation) {
        TbStoreSameDateComparisonRespVO r = new TbStoreSameDateComparisonRespVO();
        // 过滤出格式化返回值需要的数据列表 因为获取时需要超日期范围获取数据 例如 单位 日 关系 同期 就要多获取7天根这7天前的数据进行计算
        List<TbStoreData> resUseList = list.stream()
                .filter(i ->
                        (reqStartDate.isBefore(i.getDataDate()) || reqStartDate.isEqual(i.getDataDate()))
                                && (reqEndDate.isAfter(i.getDataDate()) || reqEndDate.isEqual(i.getDataDate())))
                .collect(Collectors.toList());
        try {
            if (relation.equals(ContrastRelation.TQ)) {
                if (unit.equals(ContrastUnit.DAY)) {
                    tqCase1(r, list, resUseList);
                }
            } else if (relation.equals(ContrastRelation.LQ)) {
                if (unit.equals(ContrastUnit.DAY)) {
                    lqCase1(r, list, resUseList);
                } else if (unit.equals(ContrastUnit.WEEK)) {
                    lqCase2(r, list, resUseList);
                }
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new ServiceException("格式化数据时失败 " + e);
        }
        return r;
    }

    /**
     * 单位 : 日 关系 : 同期
     * 周日对比数据格式化情况 1
     */
    private static void tqCase1(TbStoreSameDateComparisonRespVO r, List<TbStoreData> baseList, List<TbStoreData> resUseList) throws NoSuchFieldException, IllegalAccessException {
        // 表头 也就是 列名
        List<Column> columns = resUseList.stream()
                .map(i -> {
                    String label = LocalDateTimeUtil.formatNormal(i.getDataDate().toLocalDate()) + " " + ContrastUnit.DAY.getColPrefix();
                    return new Column(label, "220px", null);
                }).collect(Collectors.toList());
        List<Map<String, Element>> tableData = buildTableData(columns, baseList, resUseList, date -> date.minusDays(7));
        // 列名首位追加上指数名列
        columns.add(0, new Column("指数名", "150px", "left"));
        r.setColumns(columns);
        r.setTableData(tableData);
    }

    /**
     * 单位 : 日 关系 : 临期
     * 周日对比数据格式化情况 1
     */
    private static void lqCase1(TbStoreSameDateComparisonRespVO r, List<TbStoreData> baseList, List<TbStoreData> resUseList) throws NoSuchFieldException, IllegalAccessException {
        List<Column> columns = resUseList.stream()
                .map(i -> {
                    String label = LocalDateTimeUtil.formatNormal(i.getDataDate().toLocalDate());
                    return new Column(label, "220px", null);
                }).collect(Collectors.toList());
        List<Map<String, Element>> tableData = buildTableData(columns, baseList, resUseList, date -> date.minusDays(1));
        // 列名首位追加上指数名列
        columns.add(0, new Column("指数名", "150px", "left"));
        r.setColumns(columns);
        r.setTableData(tableData);
    }

    /**
     * 单位 : 周 关系 : 临期
     * 周日对比数据格式化情况 2
     */
    private static void lqCase2(TbStoreSameDateComparisonRespVO r, List<TbStoreData> baseList, List<TbStoreData> resUseList) throws NoSuchFieldException, IllegalAccessException {
        resUseList = listDateUnitByGroupAfterSum(resUseList);
        resUseList.sort((a,b) -> b.getDataDate().compareTo(a.getDataDate()));
        List<Column> columns = resUseList.stream()
                .map(i -> {
                    String label = LocalDateTimeUtil.formatNormal(i.getDataDate().toLocalDate()) + "/" + LocalDateTimeUtil.formatNormal(i.getDataDate().toLocalDate().plusDays(6));
                    return new Column(label, "220px", null);
                }).collect(Collectors.toList());
        List<Map<String, Element>> tableData = buildTableData(columns,listDateUnitByGroupAfterSum(baseList),resUseList,date -> date.minusDays(7));
        // 列名首位追加上指数名列
        columns.add(0, new Column("指数名", "150px", "left"));
        r.setColumns(columns);
        r.setTableData(tableData);
    }

    /**
     * 对数据列表按周期(周/月 目前固定周)分组然后对数值字段求和
     * @return 求和后列表
     */
    private static List<TbStoreData> listDateUnitByGroupAfterSum(List<TbStoreData> list){
        ArrayList<TbStoreData> r = new ArrayList<>();
        Map<LocalDateTime, List<TbStoreData>> groups = new HashMap<>();
        list.sort((a, b) -> b.getDataDate().compareTo(a.getDataDate()));
        list.forEach(item -> {
            LocalDateTime date = item.getDataDate();
            LocalDateTime weekFirstDate = date.minusDays(date.getDayOfWeek().getValue() - 1L);
            groups.computeIfAbsent(weekFirstDate, k -> new ArrayList<>());
            groups.get(weekFirstDate).add(item);
        });
        groups.forEach((key, value) -> {
            try {
                r.add(toListSum(key,value));
            } catch (IllegalAccessException e) {
                throw new ServiceException(e.getMessage());
            }
        });
        return r;
    }

    private static TbStoreData toListSum(LocalDateTime keyDate,List<TbStoreData> items) throws IllegalAccessException {
        TbStoreData r = new TbStoreData().setDataDate(keyDate);
        for (Field field : getNumericalField()) {
            field.setAccessible(true);
            if (field.getType().equals(BigDecimal.class)){
                field.set(r,BigDecimal.ZERO);
            }else if (field.getType().equals(Integer.class)){
                field.set(r,0);
            }else if (field.getType().equals(Long.class)){
                field.set(r,0L);
            }
        }
        for (TbStoreData item : items) {
            for (Field field : getNumericalField()) {
                field.set(r,CalculateUtils.sumTwoValue(field,r,item));
            }
        }
        return r;
    }

    /**
     * 构建表格数据
     *
     * @param columns    列信息
     * @param baseList   数据库查出的列表
     * @param resUseList 构建数据具体使用的列表
     * @param cDate      获取当前遍历日期应该对比的日期的key值的函数接口
     */
    public static List<Map<String, Element>> buildTableData(List<Column> columns, List<TbStoreData> baseList, List<TbStoreData> resUseList, UnaryOperator<LocalDateTime> cDate) throws NoSuchFieldException, IllegalAccessException {
        // 方便快速定位同期数据
        Map<LocalDateTime, TbStoreData> map = baseList.stream().collect(Collectors.toMap(TbStoreData::getDataDate, i -> i));
        //初始化表格数据
        ArrayList<Map<String, Element>> tableData = new ArrayList<>();
        // 反射 获取到字段列表
        List<Field> fields = getFieldList();
        // 提前获取数据日期字段对线方便使用
        Field dataDateFiled = getDataDateField();
        for (Field f : fields) {
            f.setAccessible(true);
            // 获取字段对应的在前端显示的中文名
            String fieldLabel = FIELD_LABEL_MAP.get(f.getName());
            if (StrUtil.isBlank(fieldLabel)) {
                continue;
            }
            // 初始化当前行数据 key: 对应列名 value 元素值以及差值对象
            HashMap<String, Element> lineData = new HashMap<>(resUseList.size());
            // 添加指数名列
            lineData.put("指数名", new Element().setValue(fieldLabel));
            for (int i = 0; i < columns.size(); i++) {
                // 获取当前数据日期需要对比的日期数据对象
                LocalDateTime date = (LocalDateTime) dataDateFiled.get(resUseList.get(i));
                TbStoreData cEntity = map.get(cDate.apply(date));
                // 反射获取需要对比的字段的数据值
                Object v1 = f.get(resUseList.get(i));
                Object v2 = cEntity != null ? f.get(cEntity) : null;
                // 计算两个值的差值 以及 差值百分比
                BigDecimal[] diff = CalculateUtils.calculateDiff(v1, v2);
                // 追加到当前行中
                lineData.put(columns.get(i).getLabel(), new Element().setValue(v1 == null ? 0 : v1).setPoor(diff[0]).setPercentage(diff[1]));
            }
            // 将当前行追加到表格中
            tableData.add(lineData);
        }
        return tableData;
    }

    /**
     * 获取数据周期字段对象
     *
     * @return 数据周期字段对象
     * @throws NoSuchFieldException 没有此字段
     */
    public static Field getDataDateField() throws NoSuchFieldException {
        Field dataDateFiled = TbStoreData.class.getDeclaredField("dataDate");
        dataDateFiled.setAccessible(true);
        return dataDateFiled;
    }

    /**
     * 获取 TbStoreData 类的非静态字段 列表
     *
     * @return 非静态字段列表
     */
    public static List<Field> getFieldList() {
        return Arrays.stream(TbStoreData.class.getDeclaredFields())
                // 过滤掉静态字段
                .filter(f -> !Modifier.isStatic(f.getModifiers())).collect(Collectors.toList());

    }

    /**
     * 获取指数字段对象列表
     * @return 指数字段列表
     */
    public static List<Field> getNumericalField() {
        return getFieldList().stream().filter(f -> FIELD_LABEL_MAP.containsKey(f.getName())).collect(Collectors.toList());
    }



    private static final Map<String, String> FIELD_LABEL_MAP = ImmutableMap.<String, String>builder()
            .put("amountPaid", "支付金额")
            .put("liveRoomAmount", "直播间金额")
            .put("payTransformRate", "支付转化率")
            .put("refundAmount", "退款金额")
            .put("visitorsNum", "访客数量")
            .put("wxtSpend", "万象台花费")
            .put("wxtTransactionAmount", "万象台成交额")
            .put("ylmfSpend", "引力魔方花费")
            .put("ylmfTransactionAmount", "引力魔方成交额")
            .put("ztcSpend", "直通车花费")
            .put("fillOrderNum", "补单数")
            .put("brushOrdersAmount", "刷单金额")
            .put("ztcTransactionAmount", "直通车成交额")
            .build();
}
